/*******************************************************************************
 * Copyright (c) 2001-2003 Boris Pruessmann and others. Copyright (C) 2007
 * Google Inc. (Mustafa K. Isik) All rights reserved. This program and the
 * accompanying materials are made available under the terms of the Common
 * Public License v0.5 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors: Boris Pruessmann - Initial implementation. David Corbin - added
 * MoveDeleteHook. - modify checkout, uncheckout to IClientApi, not PerforceUtil
 * Mustafa K. Isik, Google Inc. - codesurgeon@gmail.com linked resources,
 * adaptation of Eclipse 3.3 SDK, Java 1.5
 *
 ******************************************************************************/
package net.sourceforge.perforce.core.internal;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import net.sourceforge.perforce.core.IP4TeamProvider;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.ResolveTypeEnum;
import net.sourceforge.perforce.core.internal.api.PerforceServerManager;
import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.resources.RemoteFile;
import net.sourceforge.perforce.core.resources.RemoteFolder;
import net.sourceforge.perforce.core.resources.RemoteResource;
import net.sourceforge.perforce.core.syncinfo.FolderSyncInfo;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.team.IMoveDeleteHook;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @version $Revision: 494 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 * @author <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
 */
public class P4TeamProvider extends RepositoryProvider implements IP4TeamProvider {
  private static final boolean TRACE =
      getDebugOptionAsBoolean(PerforceCorePlugin.ID + "/p4teamprovider/trace");

  private static boolean getDebugOptionAsBoolean(String option) {
    return "true".equalsIgnoreCase(Platform.getDebugOption(option));
  }

  /** Stores the associated PerforceServer instance. */
  private IPerforceServer perforceServer;
  /** Stores the associated Eclipse Project. */
  private IProject project;
  /** Stores the IFileModificationValidator instance to be used. */
  private org.eclipse.core.resources.team.FileModificationValidator fileModificationValidator;
  /** Stores the IMoveDeleteHook instance to be used. */
  private IMoveDeleteHook moveDeleteHook;

  private static void trace(String fmt, Object... params) {
    if (TRACE
        && !Thread.currentThread().getName().equals("Perforce Background Addition Marker Manager")
    // && !Thread.currentThread().getName().equals("ModalContext")
    )// TODO get rid of all thread-conditionals
    {
      System.out.print("P4TeamProvider ");
      System.out.printf(fmt, params);
      System.out.print("\n executed by thread " + Thread.currentThread().getName());
      System.out.println();
    }
  }

  /**
   * Call this method to retrieve the P4TeamProvider that is responsible for the
   * given resource. If the resource is not currently under control of a
   * P4TeamProvider, null is returned.
   *
   * @param resource the resource.
   * @return the responsible P4TeamProvider.
   */
  public static P4TeamProvider getProvider(IResource resource) {
    /*
     * trace("start getProvider: %s \n resolves to %s ",
     * String.valueOf(resource), resource.getLocation().toOSString());
     */
    try {
      IProject project = resource.getProject();
      if (project == null) {
        return null;
      }

      RepositoryProvider provider = RepositoryProvider.getProvider(project);
      if (provider != null && provider.getID().equals(PerforceCorePlugin.getTypeId())) {
        return (P4TeamProvider) provider;
      }

      return null;
    } finally {
      // trace("end getProvider");
    }
  }

  /** Constructor for P4TeamProvider. */
  public P4TeamProvider() {
  }

  /**
   * Returns whether or not linked resources are supported by this plug-in.
   *
   * @return whether this repository provider can handle linked resources that
   *         are located via a URI or occur at an arbitrary depth in the project
   *
   * @see RepositoryProvider#canHandleLinkedResourceURI()
   *
   * @since 3.2
   */
  @Override
  public boolean canHandleLinkedResourceURI() {
    return true; // TODO: Mustafa: when returning "true" all implemented
                 // operations need to be aware of proper linked resource
                 // handling
  }

  /**
   * Returns the associated PerforceServer instance.
   *
   * @return the PerforceServer.
   */
  public IPerforceServer getPerforceServer() {
    Policy.assertNotNull(perforceServer);
    return perforceServer;
  }

  @Override
  public String getID() {
    return PerforceCorePlugin.getTypeId();
  }

  @Override
  public void configureProject() {
  }

  public void deconfigure() {
  }

  @Override
  public IProject getProject() {
    Policy.assertNotNull(project);
    return project;
  }

  @Override
  public void setProject(IProject project) {
    trace("start setProject [%s]", String.valueOf(project));
    try {
      Policy.assertNotNull(project);
      this.project = project;

      try {
        PerforceServerManager manager =
            (PerforceServerManager) PerforceCorePlugin.getServerManager();
        perforceServer = manager.getAssociatedPerforceServer(project);
      } catch (PerforceException e) {
        PerforceCorePlugin.log(e.getStatus());
      }
    } finally {
      trace("end setProject");
    }
  }

  /**
   * Call this method to open the given resources for edit.
   *
   * @param resources array of resources that should be opened for edit.
   * @param depth indicates the depth of the operation.
   * @param monitor used for progress feedback.
   *
   * @throws PerforceException in case of an exceptional error.
   */
  public void checkout(IResource[] resources, int depth, IProgressMonitor monitor)
      throws PerforceException {
    IStatus status = openForEdit(resources, depth, monitor);
    if (!status.isOK()) {
      throw new PerforceException(status);
    }
  }

  /**
   * This method sync the given resources with the head revision as stored on
   * the Perforce Server.
   *
   * @param resources array of resources to sync.
   * @param depth indicates the depth of the operation.
   * @param progress used for progress feedback.
   *
   * @throws TeamException indicates an exceptional error situation.
   */
  public void get(IResource[] resources, int depth, IProgressMonitor progress) throws TeamException {
    trace("start get");
    try {
      MultiStatus status = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);
      progress = Policy.monitorFor(progress);

      progress.beginTask("Syncing ", resources.length);
      try {
        // we currently only support get of files, so this one is easy
        for (int i = 0; i < resources.length; i++) {
          Policy.assertTrue(resources[i].getType() == IResource.FILE);

          LocalFile localFile = (LocalFile) LocalResource.createFromResource(resources[i]);

          progress.subTask(localFile.getSyncInfo().getDepotFileName());
          status.merge(localFile.sync("#head", progress));

          if (!status.isOK()) {
            throw new PerforceException(status);
          }
        }
      } finally {
        try {
          for (int i = 0, length = resources.length; i < length; i++) {
            resources[i].refreshLocal(IResource.DEPTH_ONE, progress);
          }
        } catch (CoreException e) {
          throw new TeamException(e.getStatus());
        } finally {
          // refreshResourceStates(resources, null);//TODO: MKI: change this to
          // not call refresh Resource States directly anymore
          refreshState(resources, depth, new SubProgressMonitor(progress, resources.length));
          progress.done();
        }
      }
    } finally {
      trace("end get");
    }
  }

  /**
   * TODO:
   *
   * @param resource the resource for which we need an RemoteResource.
   * @param revision the revision we need.
   * @param progress used for progress feedback.
   * @return the RemoteResource.
   *
   * @throws TeamException in case of an exception.
   */
  public RemoteResource getRemoteTree(IResource resource, String revision, IProgressMonitor progress)
      throws TeamException {
    trace("start getRemoteTree [%s][%s]", String.valueOf(resource), revision);
    try {
      RemoteResource result = null;
      if (resource.getType() == IResource.FILE) {
        // p4 print file#have seems to be slower than p4 print file#haveRev ...
        if (revision.equals("#have")) {
          LocalResource file = LocalResource.createFromResource(resource);
          revision = "#" + file.getSyncInfo().getHaveRev();
        }

        String filename =
            getPerforceServer().where(resource.getLocation().toOSString()).getDepotFilename();
        result = new RemoteFile(getPerforceServer(), filename, revision);
      } else {
        FolderSyncInfo folderSyncInfo =
            PerforceSynchronizer.getInstance().getFolderSync((IContainer) resource);
        result = new RemoteFolder(getPerforceServer(), null, folderSyncInfo, revision);
      }

      return result;
    } finally {
      trace("end getRemoteTree");
    }
  }

  public boolean hasRemote(IResource resource) {
    LocalResource localResource = LocalResource.createFromResource(resource);
    return localResource != null ? localResource.hasRemote() : false;
  }

  public boolean isIgnored(IResource resource) {
    LocalResource localResource = LocalResource.createFromResource(resource);
    return localResource != null ? localResource.isIgnored() : false;
  }

  public boolean isCheckedOut(IResource resource) {
    LocalResource localResource = LocalResource.createFromResource(resource);
    return localResource != null ? localResource.isCheckedOut() : false;
  }

  public boolean isResolved(IResource resource) {
    LocalResource localResource = LocalResource.createFromResource(resource);
    return localResource != null ? localResource.isResolved() : false;
  }

  /**
   * Call this method to refresh the team provider state of the given resources.
   *
   * @param resources the resources to refresh.
   * @param depth the depth of the operation.
   * @param monitor used for progress feedback.
   */
  public void refreshState(IResource[] resources, int depth, IProgressMonitor monitor) {
    final List<IFile> filesToRefresh =
        getFilesForResources(resources, depth, Policy.subMonitorFor(monitor, resources.length));
    final int resourceCount = filesToRefresh.size();

    monitor = Policy.monitorFor(monitor);
    monitor.beginTask("Refreshing " + resourceCount + " resources", resourceCount);

    trace("refreshState is passing %s resources to ResourceRefresherJob", filesToRefresh.size());
    Job refreshJob =
        new ResourceRefresherJob(getPerforceServer(), filesToRefresh.toArray(new IResource[0]));

    refreshJob.schedule();
    monitor.done();
  }

  /**
   * Finds all <code>IFile</code>s for every <code>IResource</code> in resources
   * up to specified <code>depth</code>.
   *
   * Supports linked resources. <code>IResource</code>s that are linked are
   * resolved to absolute file-system locations in order to find children.
   *
   * @param resources to be resolved to files on the file-system
   * @param depth <code>IResource</code> depth code
   * @return List of <code>IFile</code>s
   * @see org.eclipse.core.resources.IResource#getLocation()
   * @see org.eclipse.core.resources.IWorkspaceRoot#findFilesForLocation(IPath)
   */
  public List<IFile> getFilesForResources(IResource[] resources, int depth, IProgressMonitor monitor) {
    trace("START getFilesForResources");
    try {
      monitor = Policy.monitorFor(monitor);
      monitor
          .beginTask("Getting files for " + resources.length + "resources ...", resources.length);
      final List<IFile> files = Lists.newArrayList();
      final IWorkspaceRoot workspaceRoot = PerforceCorePlugin.getWorkspace().getRoot();
      for (int i = 0; i < resources.length; i++) {
        try {
          if (resources[i].exists()) {
            resources[i].refreshLocal(depth, null);// TODO: Mustafa: supply a
                                                   // progress monitor here!
            resources[i].accept(new IResourceVisitor() {
              public boolean visit(IResource resource) {
                IPath locationResolvedIfLink = resource.getLocation();
                /*
                 * Absolute paths are required for
                 * IWorkspaceRoot.findFilesForLocation(IPath) Relative paths
                 * passed into the method get treated as absolute!
                 */
                if (!locationResolvedIfLink.isAbsolute()) {
                  locationResolvedIfLink = locationResolvedIfLink.makeAbsolute();
                }
                IFile[] filesForLocation =
                    workspaceRoot.findFilesForLocation(locationResolvedIfLink);

                files.addAll(Arrays.asList(filesForLocation));
                return true;
              }
            }, IResource.DEPTH_INFINITE, false);
          }
          monitor.worked(1);
        } catch (CoreException e) {
          PerforceCorePlugin.logError("Local Refresh of IResource failed: "
              + resources[i].getFullPath().toOSString(), e);
        } finally {
          monitor.done();
        }
      }
      return files;
    } finally {
      trace("END getFilesForResources");
    }
  }

  /**
   * Sync the given resources with the perforce server.
   *
   * @param resources the resources to sync.
   * @param depth the depth of the operation.
   * @param progress used for progress feedback.
   *
   * @throws TeamException in case of an error.
   */
  public void sync(IResource[] resources, int depth, IProgressMonitor progress)
      throws TeamException {
    trace("start sync");
    progress = Policy.monitorFor(progress);
    progress.beginTask("Sync in progress ...", resources.length); //$NON-NLS-1$

    try {
      MultiStatus status =
          new MultiStatus(PerforceCorePlugin.ID, MultiStatus.OK, "Problem during 'p4 sync'.", null);

      for (int i = 0; i < resources.length; i++) {
        final IPath location = resources[i].getLocation();
        progress.subTask("Syncing " + getDisplayName(location));
        String syncString = location.toOSString();

        if (resources[i].getType() != IResource.FILE) {
          switch (depth) {
            case IResource.DEPTH_ZERO:
              break;

            case IResource.DEPTH_ONE:
              syncString += File.separator + "*";//$NON-NLS-1$
              break;

            case IResource.DEPTH_INFINITE:
              syncString += File.separator + "...";//$NON-NLS-1$
              break;

            default:
              Policy.assertTrue(false);
          }
        }
        status.merge(getPerforceServer().sync(syncString, "#head", progress));
        progress.worked(1);
      }

      if (status.getSeverity() >= IStatus.WARNING) {
        throw new PerforceException(status);
      }

      List<IFile> resourcesToBeRefreshed =
          getFilesForResources(resources, depth, Policy.subMonitorFor(progress, resources.length));
      resources = resourcesToBeRefreshed.toArray(new IResource[resourcesToBeRefreshed.size()]);
      refreshResources(resources, Policy.subMonitorFor(progress, resources.length));
    } finally {
      progress.done();
      trace("end sync");
    }
  }

  @Override
  public synchronized org.eclipse.core.resources.team.FileModificationValidator
      getFileModificationValidator() {
    if (fileModificationValidator == null) {
      fileModificationValidator = new FileModificationValidator(this);
    }

    return fileModificationValidator;
  }

  @Override
  public IMoveDeleteHook getMoveDeleteHook() {
    if (moveDeleteHook == null) {
      moveDeleteHook = new MoveDeleteHook(this);
    }

    return moveDeleteHook;
  }

  public IStatus openForAdd(IResource[] resources, int depth, IProgressMonitor monitor) {
    trace("start openForAdd");
    try {
      // get all files at resource (file/folder) locations specified by
      // parameter
      List<IFile> affectedFilesList =
          getFilesForResources(resources, depth, Policy.subMonitorFor(monitor, resources.length));
      /*
       * remove those resources that do not qualify - i.e. those that do already
       * have a remote object representation
       */
      for (Iterator<IFile> filesIterator = affectedFilesList.iterator(); filesIterator.hasNext();) {
        IFile maybeAffectedFile = filesIterator.next();
        if (hasRemote(maybeAffectedFile)) {
          filesIterator.remove();
        }
      }

      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("Open for Add: ", affectedFilesList.size() * 1000 + 1000);

      MultiStatus result = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);//$NON-NLS-1$

      try {
        getPerforceServer().add(reduceToUniquePaths(affectedFilesList), monitor);
      } catch (PerforceException e) {
        result.merge(e.getStatus());
      }

      monitor.worked(1000);

      resources = affectedFilesList.toArray(new IResource[affectedFilesList.size()]);
      refreshResources(resources, monitor);

      monitor.done();
      return result;
    } finally {
      trace("end openForAdd");
    }
  }

  /**
   * Reduces a list of resources to an array of unique pathnames.
   *
   * Resources that resolve to the same locations on the filesystem are treated
   * as redundant. Linked Resources are supported by being resolved to the
   * actual content location.
   *
   * @param resources to be resolved to filesystem pathnames
   * @return unique pathnames
   */
  private String[] reduceToUniquePaths(List<? extends IResource> resources) {
    Set<String> resolvedResourceLocations = Sets.newHashSet();
    for (IResource resource : resources) {
      resolvedResourceLocations.add(getResourceLocation(resource));
    }
    return resolvedResourceLocations.toArray(new String[resolvedResourceLocations.size()]);
  }

  /** @see IP4TeamProvider#openForDelete(IResource[], int, IProgressMonitor) */
  public IStatus openForDelete(IResource[] resources, int depth, IProgressMonitor monitor) {
    trace("start openForDelete");
    try {
      // get all files at resource (file/folder) locations specified by
      // parameter
      List<IFile> affectedFilesList =
          getFilesForResources(resources, depth, Policy.subMonitorFor(monitor, resources.length));
      /*
       * remove those resources that do not qualify - i.e. those that do either
       * have no remote representation according to
       * P4TeamProvider.hasRemote(IResource) or are to be ignored according to
       * P4TeamProvider.isIgnored(IResource) or are not of type IResource.FILE
       */
      for (Iterator<IFile> filesIterator = affectedFilesList.iterator(); filesIterator.hasNext();) {
        IFile maybeAffectedFile = filesIterator.next();
        if (!hasRemote(maybeAffectedFile) || isIgnored(maybeAffectedFile)) {
          filesIterator.remove();
        }
      }

      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("Open for Delete: ", affectedFilesList.size() * 1000 + 1000);

      MultiStatus result = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);//$NON-NLS-1$

      try {
        getPerforceServer().delete(reduceToUniquePaths(affectedFilesList));
      } catch (PerforceException e) {
        result.merge(e.getStatus());
      }
      monitor.worked(1000);

      /*
       * in order to refresh correctly, check whether the (possibly linked
       * resource) parent still exists, i.e. contains any resources not opened
       * for delete - if not the refresh on parent would fail, since the parent
       * would have been removed also
       */
      HashMap<IContainer, LinkedList<IFile>> parentToChildrenMapping = Maps.newHashMap();
      IContainer parent;
      for (IFile file : affectedFilesList) {
        parent = file.getParent();
        // create parent-children mapping if it does not exist
        if (!parentToChildrenMapping.containsKey(parent)) {
          parentToChildrenMapping.put(parent, new LinkedList<IFile>());
        }
        parentToChildrenMapping.get(parent).add(file);
      }
      // check contents of every parent and compare to # of affected children
      Set<IContainer> parentsToBeRefreshed = parentToChildrenMapping.keySet();
      for (Iterator<IContainer> parentIterator = parentsToBeRefreshed.iterator(); parentIterator
          .hasNext();) {
        parent = parentIterator.next();
        if (parent.exists()) {
          try {
            // don't refresh parent if all children are bound for deletion
            if (parent.members().length == parentToChildrenMapping.get(parent).size()) {
              parentIterator.remove();// get this item's parent before removing!
            }
          } catch (CoreException e) {
            PerforceCorePlugin.log(e.getStatus());
          }
        }
      }

      IResource[] refresh =
          parentsToBeRefreshed.toArray(new IResource[parentsToBeRefreshed.size()]);
      refreshResources(refresh, monitor);

      monitor.done();
      return result;
    } finally {
      trace("end openForDelete");
    }
  }

  @SuppressWarnings("unused")
  public IStatus openForEdit(IResource[] resources, int depth, IProgressMonitor monitor)
      throws PerforceException {
    trace("START openForEdit");
    long startTime = System.currentTimeMillis(); // TODO unneeded when not in
                                                 // trace mode
    long timeDelta = 0; // TODO unneeded when not in trace mode

    try {
      List<IFile> affectedFilesList =
          getFilesForResources(resources, depth, Policy.subMonitorFor(monitor, resources.length));
      trace("\topenForEdit got files for resources took: %s",
          (float) (System.currentTimeMillis() - startTime) / 1000);
      /*
       * remove those resources that do not qualify - i.e. those that do not
       * have a remote object representation
       */
      trace("\topenForEdit iterating over affectedFilesList to remove such without remote");
      timeDelta = System.currentTimeMillis();
      for (Iterator<IFile> filesIterator = affectedFilesList.iterator(); filesIterator.hasNext();) {
        IFile maybeAffectedFile = filesIterator.next();
        /*
         * trace("\t\topenForEdit checking for remote FILE: %s",
         * maybeAffectedFile.getFullPath().toOSString());
         */
        if (!hasRemote(maybeAffectedFile)) {
          filesIterator.remove();
        }
      }
      trace("\topenForEdit done iterating for removable files in %s", (float) (System
          .currentTimeMillis() - timeDelta) / 1000);

      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("Open For Edit: ", affectedFilesList.size() * 1000 + 1000);

      MultiStatus result = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);//$NON-NLS-1$

      try {
        trace("\topenForEdit submitting %s files to PerforceServer.edit(...)", affectedFilesList
            .size());
        timeDelta = System.currentTimeMillis();
        getPerforceServer().edit(reduceToUniquePaths(affectedFilesList), monitor);
      } catch (PerforceException e) {
        result.merge(e.getStatus());
      } finally {
        trace("\topenForEdit back from PerforceServer.edit(...) in %s", (float) (System
            .currentTimeMillis() - timeDelta) / 1000);
      }
      resources = affectedFilesList.toArray(new IResource[affectedFilesList.size()]);
      trace("\topenForEdit refreshing resources");
      timeDelta = System.currentTimeMillis();
      refreshResources(resources, monitor);
      trace("\topenForEdit done refreshing in %s",
          (float) (System.currentTimeMillis() - timeDelta) / 1000);
      monitor.done();
      return result;
    } finally {
      trace("END openForEdit - ALL COMPLETED IN %s seconds",
          (float) (System.currentTimeMillis() - startTime) / 1000);
    }
  }

  public IStatus revert(IResource[] resources, int depth, IProgressMonitor monitor) {
    trace("START revert");
    long startTime = System.currentTimeMillis(); // TODO unneeded when not in
                                                 // trace mode
    long timeDelta = 0; // TODO unneeded when not in trace mode
    try {
      // get all files at resource (file/folder) locations specified by
      // parameter
      List<IFile> affectedFilesList =
          getFilesForResources(resources, depth, Policy.subMonitorFor(monitor, resources.length));
      trace("\trevert got files for resources took: %s",
          (float) (System.currentTimeMillis() - startTime) / 1000);
      /*
       * remove those resources that do not qualify - i.e. those that do already
       * have a remote object representation
       */
      trace("\trevert iterating over affectedFilesList to remove such "
          + "without remote (logic) OR not checked out");
      timeDelta = System.currentTimeMillis();
      for (Iterator<IFile> filesIterator = affectedFilesList.iterator(); filesIterator.hasNext();) {
        IFile file = filesIterator.next();
        if (!(hasRemote(file) && isCheckedOut(file))) {
          filesIterator.remove();
        }
      }
      trace("revert done iterating for removable files in %s",
          (float) (System.currentTimeMillis() - timeDelta) / 1000);

      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("Revert: ", affectedFilesList.size() * 1000 + 1000);

      MultiStatus result = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);//$NON-NLS-1$

      try {
        trace("revert submitting %s files to PerforceServer.revert(...)", affectedFilesList.size());
        timeDelta = System.currentTimeMillis();
        getPerforceServer().revert(reduceToUniquePaths(affectedFilesList), null);
      } catch (PerforceException e) {
        result.merge(e.getStatus());
      } finally {
        trace("revert back from PerforceServer.revert(...) in %s", (float) (System
            .currentTimeMillis() - timeDelta) / 1000);
      }

      monitor.worked(1000);

      resources = affectedFilesList.toArray(new IResource[affectedFilesList.size()]);
      trace("revert refreshing resources");
      timeDelta = System.currentTimeMillis();
      refreshResources(resources, monitor);

      trace("revert done refreshing in %s", (float) (System.currentTimeMillis() - timeDelta) / 1000);

      monitor.done();
      return result;
    } finally {
      trace("END revert - ALL COMPLETED IN %s seconds",
          (float) (System.currentTimeMillis() - startTime) / 1000);
    }

  }

  public IStatus markAsResolved(IResource[] resources, IProgressMonitor monitor)
      throws PerforceException {
    trace("start markAsResolved");
    try {
      monitor = Policy.monitorFor(monitor);
      monitor.beginTask("Mark As Resolved: ", resources.length * 1000 + 1000);
      try {
        MultiStatus result = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null); //$NON-NLS-1$

        for (int i = 0; i < resources.length; i++) {
          monitor.subTask(resources[i].getLocation().toOSString());

          getPerforceServer().resolve(resources[i].getLocation().toOSString(),
              ResolveTypeEnum.ACCEPT_YOURS, 0);

          monitor.worked(1000);
        }

        return result;
      } finally {
        refreshResources(resources, monitor);

        monitor.done();
      }
    } finally {
      trace("end markAsResolved");
    }
  }


  /**
   * Generates the display name for the given path.
   *
   * @param path the path.
   * @return the filename to display.
   */
  private String getDisplayName(IPath path) {
    String[] segments = path.segments();
    if (segments.length < 4) {
      return path.toOSString();
    }

    StringBuilder result = new StringBuilder("...");
    for (int i = segments.length - 3; i < segments.length; i++) {
      result.append(File.separatorChar).append(segments[i]);
    }

    return result.toString();
  }

  // /** Called to retrieve an array of resource locations from the List of
  // IResources.
  // *
  // * This method handles linked resources by resolving paths to the actual
  // * content locations on the filesystem.
  // *
  // * @param resourceList list of resources.+
  // * @return array of locations as string.
  // * @see org.eclipse.core.resources.IResource#getLocation()
  // */
  // private String[] getResourceLocations(List<? extends IResource>
  // resourceList)
  // {
  // String[] result = new String[resourceList.size()];
  // for (int j = 0; j < resourceList.size(); j++)
  // {
  // result[j] = getResourceLocation(resourceList.get(j));
  // }
  //
  // return result;
  // }

  private String getResourceLocation(IResource resource) {
    return resource.getLocation().toOSString();
  }

  /**
   * Called to refresh the given resources and their resource states.
   *
   * @param resources the resources to refresh,
   * @param monitor the progress monitor.
   */
  private void refreshResources(IResource[] resources, IProgressMonitor monitor) {
    refreshState(resources, IResource.DEPTH_INFINITE, monitor);// TODO: MKI:
                                                               // changed this
                                                               // from DEPTH_ONE
  }

  // /** Returns a String that describes the size of the given array.
  // *
  // * @param fileSet some objects.
  // * @return the String representation.
  // */
  // private String fileSetDescriptor(Object[] fileSet)
  // {
  // if (fileSet.length == 1)
  // {
  // return fileSet[0].toString();
  // }
  // else
  // if (fileSet.length == 0)
  // {
  // return "<none>";
  // }
  // else
  // {
  // return "<" + fileSet.length + " files>";
  // }
  // }

  // /** Provides wrapper for fileSetDescriptor(Object[]) that can be used for
  // Lists.
  // *
  // * @param fileSet some objects as list.
  // * @return the String representation.
  // *
  // * @see P4TeamProvider#fileSetDescriptor(Object[])
  // */
  // private String fileSetDescriptor(List fileSet)
  // {
  // return fileSetDescriptor(fileSet.toArray());
  // }
  //
  // /** Broadcast changes to the specified resources.
  // *
  // * @param resources array of changed resources.
  // * @param monitor used for progress feedback.
  // */
  // private void refreshResourceStates(IResource[] resources, IProgressMonitor
  // monitor)
  // {
  // trace("START refreshResourceStates");
  // long startTime = System.currentTimeMillis(); //TODO unneeded when not in
  // trace mode
  //
  // try
  // {
  // PerforceSynchronizer synchronizer = PerforceCorePlugin.getSynchronizer();
  // synchronizer.beginOperation(null);
  // try
  // {
  // monitor.beginTask("", resources.length * 300);
  //
  // monitor.subTask("Collecting synchronization info...");
  // Map<IResource, ResourceSyncInfo> resourceStateMap =
  // new HashMap<IResource, ResourceSyncInfo>();
  //
  // int j = 0;
  // List<IResource> fileList = new ArrayList<IResource>(50);
  // for (int i = 0; i < resources.length; i++)
  // {
  // if (resources[i].getType() == IResource.FILE)
  // {
  // fileList.add(resources[i]);
  // if (++j % 50 == 0)
  // {
  // monitor.subTask("Collecting synchronization info... [" + j + "]");
  //
  // IFile[] files = fileList.toArray(new IFile[fileList.size()]);
  // ResourceSyncInfo[] syncInfos = getPerforceServer().fstat(files, null);
  // for (int k = 0; k < syncInfos.length; k++)
  // {
  // if (syncInfos[k] != null && !syncInfos[k].isDeleted())
  // {
  // resourceStateMap.put(files[k], syncInfos[k]);
  // }
  // else
  // {
  // resourceStateMap.put(files[k], null);
  // }
  // }
  //
  // monitor.worked(2500);
  // fileList.clear();
  // }
  //
  // monitor.worked(50);
  // }
  // }
  //
  // if (j % 50 > 0)
  // {
  // monitor.subTask("Collecting synchronization info... [" + j + "]");
  //
  // IFile[] files = fileList.toArray(new IFile[fileList.size()]);
  // ResourceSyncInfo[] syncInfos = getPerforceServer().fstat(files, null);
  // for (int k = 0; k < syncInfos.length; k++)
  // {
  // if (syncInfos[k] != null && !syncInfos[k].isDeleted())
  // {
  // resourceStateMap.put(files[k], syncInfos[k]);
  // }
  // else
  // {
  // resourceStateMap.put(files[k], null);
  // }
  // }
  //
  // monitor.worked((j % 50) * 50);
  // fileList.clear();
  // }
  //
  // for (Iterator<Map.Entry<IResource, ResourceSyncInfo>> it =
  // resourceStateMap.entrySet().iterator(); it.hasNext();)
  // {
  // Map.Entry<IResource, ResourceSyncInfo> entry = it.next();
  //
  // IResource resource = entry.getKey();
  // ResourceSyncInfo syncInfo = entry.getValue();
  //
  // if (syncInfo == null)
  // {
  // synchronizer.deleteResourceSync(resource);
  // }
  // else
  // {
  // synchronizer.setResourceSync(resource, syncInfo);
  // }
  //
  // monitor.worked(100);
  // }
  //
  // Map<IContainer, FolderSyncInfo> folderStateMap =
  // addAllParents(resourceStateMap, monitor);
  // for (Iterator<Map.Entry<IContainer, FolderSyncInfo>> it =
  // folderStateMap.entrySet().iterator(); it.hasNext();)
  // {
  // Map.Entry<IContainer, FolderSyncInfo> entry = it.next();
  //
  // IContainer resource = entry.getKey();
  // FolderSyncInfo syncInfo = entry.getValue();
  //
  // if (syncInfo == null)
  // {
  // synchronizer.deleteFolderSync(resource);
  // }
  // else
  // {
  // synchronizer.setFolderSync(resource, syncInfo);
  // }
  // }
  // }
  // catch (TeamException e)
  // {
  // PerforceCorePlugin.log(e.getStatus());
  // }
  // catch (CoreException e)
  // {
  // PerforceCorePlugin.log(e.getStatus());
  // }
  // finally
  // {
  // try
  // {
  // synchronizer.endOperation(monitor);
  // }
  // catch (PerforceException e)
  // {
  // PerforceCorePlugin.log(e.getStatus());
  // }
  //
  // monitor.done();
  // }
  // }
  // finally
  // {
  // trace("END refreshResourceStates - completed in %s seconds",
  // (float)(System.currentTimeMillis() - startTime)/1000);
  // }
  // }

  // /** Adds all parent resource (and their FolderSyncInfo) to the given map
  // and returns the
  // * resulting new map.
  // *
  // * @param resourceStateMap map containing resources.
  // * @param monitor for progress feedback.
  // * @return the new map.
  // *
  // * @throws CoreException in case of a core error.
  // * @throws PerforceException in case of an unexpected error.
  // */
  // private Map<IContainer, FolderSyncInfo> addAllParents(
  // Map<IResource, ResourceSyncInfo> resourceStateMap,
  // IProgressMonitor monitor)
  // throws PerforceException, CoreException
  // {
  // trace("start addAllParents");
  // try
  // {
  // Map<IContainer, FolderSyncInfo> result =
  // new HashMap<IContainer, FolderSyncInfo>();
  // for (Iterator<Map.Entry<IResource, ResourceSyncInfo>> it =
  // resourceStateMap.entrySet().iterator(); it.hasNext();)
  // {
  // Map.Entry<IResource, ResourceSyncInfo> entry = it.next();
  //
  // IResource resource = entry.getKey();
  // ResourceSyncInfo syncInfo = entry.getValue();
  // if (syncInfo != null)
  // {
  // String depotFilename = syncInfo.getDepotFileName();
  //
  // IContainer parent = resource.getParent();
  // while (
  // parent != null &&
  // parent.getType() != IResource.ROOT &&
  // !result.containsKey(parent))
  // {
  // depotFilename = depotFilename.substring(0, depotFilename.lastIndexOf('/'));
  //
  // FolderSyncInfo folderSyncInfo = new FolderSyncInfo(depotFilename);
  // result.put(parent, folderSyncInfo);
  // parent = parent.getParent();
  // }
  // }
  // else
  // {
  // IContainer parent = resource.getParent();
  // while (
  // parent != null &&
  // parent.getType() != IResource.ROOT &&
  // !result.containsKey(parent))
  // {
  // PerforceSynchronizer synchronizer = PerforceSynchronizer.getInstance();
  // IResource[] children = parent.members();
  //
  // FolderSyncInfo folderSyncInfo = null;
  // for (int i = 0; i < children.length && folderSyncInfo == null; i++)
  // {
  // IResource child = children[i];
  //
  // if (child.getType() == IResource.FILE)
  // {
  // ResourceSyncInfo info = synchronizer.getResourceSync(child);
  // if (info != null)
  // {
  // String depotFilename = info.getDepotFileName();
  // depotFilename =
  // depotFilename.substring(0, depotFilename.lastIndexOf('/'));
  // folderSyncInfo = new FolderSyncInfo(depotFilename);
  // }
  // }
  // else
  // if (child.getType() == IResource.FOLDER)
  // {
  // FolderSyncInfo info = null;
  // if (result.containsKey(child))
  // {
  // info = result.get(child);
  // }
  // else
  // {
  // info = synchronizer.getFolderSync((IContainer) child);
  // }
  //
  // if (info != null)
  // {
  // String depotFilename = info.getDepotFileName();
  // depotFilename =
  // depotFilename.substring(0, depotFilename.lastIndexOf('/'));
  // folderSyncInfo = new FolderSyncInfo(depotFilename);
  // }
  // }
  // }
  //
  // result.put(parent, folderSyncInfo);
  // parent = parent.getParent();
  // }
  // }
  // monitor.worked(100);
  // }
  // //result.putAll(resourceStateMap);
  // return result;
  // }
  // finally
  // {
  // trace("end addAllParents");
  // }
  // }

  /**
   * Convenience method that maps the given resources to their providers. The
   * returned Map has keys which are ITeamProviders, and values which are
   * Lists of IResources that are shared with that provider.
   *
   * @param resources the resources
   * @return a Map of mapping providers to their resources
   */

  public static Map<RepositoryProvider, List<IResource>> getProviderMapping(IResource[] resources,
      IProgressMonitor monitor) {
    HashMap<RepositoryProvider, List<IResource>> result = Maps.newHashMap();
    for (int i = 0; i < resources.length; i++) {
      RepositoryProvider provider = RepositoryProvider.getProvider(resources[i].getProject());
      if (provider == null) {
        continue;
      }

      List<IResource> list = result.get(provider);
      if (list == null) {
        list = Lists.newArrayList();
        result.put(provider, list);
      }
      list.add(resources[i]);
    }
    return result;
  }

}
